﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace MyFramework.Security
{
    /// <summary>
    /// Triple des protected configuration provider
    /// </summary>
    public class TripleDESProtectedConfigurationProvider : ProtectedConfigurationProvider
    {
        /// <summary>
        /// The triple DES provider
        /// </summary>
        private TripleDESCryptoServiceProvider tripleDesProvider = new TripleDESCryptoServiceProvider();

        /// <summary>
        /// The p key file path
        /// </summary>
        private string pKeyFilePath;

        /// <summary>
        /// The p name
        /// </summary>
        private string pName;

        /// <summary>
        /// Gets the key file path.
        /// </summary>
        /// <value>
        /// The key file path.
        /// </value>
        public string KeyFilePath
        {
            get { return pKeyFilePath; }
        }

        /// <summary>
        /// ProviderBase.Name
        /// </summary>
        public override string Name
        {
            get { return pName; }
        }

        /// <summary>
        /// ProviderBase.Initialize
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            pName = name;
            pKeyFilePath = config["keyFilePath"];
            ReadKey(KeyFilePath);
        }

        /// <summary>
        /// ProtectedConfigurationProvider.Encrypt
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public override XmlNode Encrypt(XmlNode node)
        {
            string encryptedData = EncryptString(node.OuterXml);

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.LoadXml("<EncryptedData>" + encryptedData + "</EncryptedData>");

            return xmlDoc.DocumentElement;
        }

        /// <summary>
        /// ProtectedConfigurationProvider.Decrypt
        /// </summary>
        /// <param name="encryptedNode"></param>
        /// <returns></returns>
        public override XmlNode Decrypt(XmlNode encryptedNode)
        {
            string decryptedData = DecryptString(encryptedNode.InnerText);

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.PreserveWhitespace = true;
            xmlDoc.LoadXml(decryptedData);

            return xmlDoc.DocumentElement;
        }

        /// <summary>
        /// Encrypts a configuration section and returns the encrypted
        /// XML as a string.
        /// </summary>
        /// <param name="encryptValue"></param>
        /// <returns></returns>
        private string EncryptString(string encryptValue)
        {
            byte[] valBytes = Encoding.Unicode.GetBytes(encryptValue);

            ICryptoTransform transform = tripleDesProvider.CreateEncryptor();

            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);
            cs.Write(valBytes, 0, valBytes.Length);
            cs.FlushFinalBlock();
            byte[] returnBytes = ms.ToArray();
            cs.Close();

            return Convert.ToBase64String(returnBytes);
        }

        /// <summary>
        /// Decrypts an encrypted configuration section and returns the
        /// unencrypted XML as a string.
        /// </summary>
        /// <param name="encryptedValue"></param>
        /// <returns></returns>
        private string DecryptString(string encryptedValue)
        {
            byte[] valBytes = Convert.FromBase64String(encryptedValue);

            ICryptoTransform transform = tripleDesProvider.CreateDecryptor();

            MemoryStream ms = new MemoryStream();
            CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);
            cs.Write(valBytes, 0, valBytes.Length);
            cs.FlushFinalBlock();
            byte[] returnBytes = ms.ToArray();
            cs.Close();

            return Encoding.Unicode.GetString(returnBytes);
        }

        /// <summary>
        /// Generates a new TripleDES key and vector and writes them
        /// to the supplied file path.
        /// </summary>
        /// <param name="filePath"></param>
        public void CreateKey(string filePath)
        {
            tripleDesProvider.GenerateKey();
            tripleDesProvider.GenerateIV();

            StreamWriter sw = new StreamWriter(filePath, false);
            sw.WriteLine(ByteToHex(tripleDesProvider.Key));
            sw.WriteLine(ByteToHex(tripleDesProvider.IV));
            sw.Close();
        }

        /// <summary>
        /// Reads in the TripleDES key and vector from the supplied
        /// file path and sets the Key and IV properties of the 
        /// TripleDESCryptoServiceProvider.
        /// </summary>
        /// <param name="filePath"></param>
        private void ReadKey(string filePath)
        {
            StreamReader sr = new StreamReader(filePath);
            string keyValue = sr.ReadLine();
            string ivValue = sr.ReadLine();
            tripleDesProvider.Key = HexToByte(keyValue);
            tripleDesProvider.IV = HexToByte(ivValue);
        }

        /// <summary>
        /// Converts a byte array to a hexadecimal string.
        /// </summary>
        /// <param name="byteArray"></param>
        /// <returns></returns>
        private string ByteToHex(byte[] byteArray)
        {
            string outString = "";

            foreach (Byte b in byteArray)
                outString += b.ToString("X2");

            return outString;
        }

        /// <summary>
        /// Converts a hexadecimal string to a byte array.
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        private byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            return returnBytes;
        }

    }
}
